package net.aiion.weave.util.cryptography;

import java.io.UnsupportedEncodingException;

public final class HexadecimalConverter
{
    private static final byte[] HEX_CHAR_TABLE = {
        (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
        (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
    };

    private static final int ASCII_DIGITS_MIN = 48;
    private static final int ASCII_DIGITS_MAX = 57;
    private static final int ASCII_DIGITS_OFFSET = ASCII_DIGITS_MIN;
    private static final int ASCII_UPPER_CASE_MIN = 65;
    private static final int ASCII_UPPER_CASE_MAX = 90;
    private static final int ASCII_UPPER_CASE_OFFSET = 55;
    private static final int ASCII_LOWER_CASE_MIN = 97;
    private static final int ASCII_LOWER_CASE_MAX = 122;
    private static final int ASCII_LOWER_CASE_OFFSET = 87;

    private HexadecimalConverter() {
    }

    private static byte convertHexCharacterToByte(final char aHexCharacter) {
        if (ASCII_DIGITS_MIN <= aHexCharacter && aHexCharacter <= ASCII_DIGITS_MAX) {
            return (byte) ((byte) aHexCharacter - ASCII_DIGITS_OFFSET);
        }

        if (ASCII_UPPER_CASE_MIN <= aHexCharacter && aHexCharacter <= ASCII_UPPER_CASE_MAX) {
            return (byte) ((byte) aHexCharacter - ASCII_UPPER_CASE_OFFSET);
        }

        if (ASCII_LOWER_CASE_MIN <= aHexCharacter && aHexCharacter <= ASCII_LOWER_CASE_MAX) {
            return (byte) ((byte) aHexCharacter - ASCII_LOWER_CASE_OFFSET);
        }

        throw new CryptographyException();
    }

    public static String convertBytesToHexString(byte[] aBytes) throws UnsupportedEncodingException {
        byte[] hexBytes = new byte[2 * aBytes.length];
        int index = 0;

        for (byte b : aBytes) {
            int v = b & 0xFF;
            hexBytes[index++] = HEX_CHAR_TABLE[v >>> 4];
            hexBytes[index++] = HEX_CHAR_TABLE[v & 0xF];
        }
        return new String(hexBytes, "ASCII");
    }

    public static byte[] convertHexStringToBytes(final String aHexString) {
        byte[] byteArray = new byte[aHexString.length() / 2];
        for (int i = 0; i < aHexString.length(); i += 2) {
            byteArray[i / 2] = (byte) ((convertHexCharacterToByte(aHexString.charAt(i)) * 16) + convertHexCharacterToByte(aHexString
                .charAt(i + 1)));
        }
        return byteArray;
    }

    public static String convertBytesToBinaryString(byte[] aBytes) {
        final StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < aBytes.length; i++) {
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 7) & 0x01));
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 6) & 0x01));
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 5) & 0x01));
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 4) & 0x01));
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 3) & 0x01));
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 2) & 0x01));
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 1) & 0x01));
            stringBuilder.append(getCharacterForBit((aBytes[i] >> 0) & 0x01));
            stringBuilder.append('.');
        }
        return stringBuilder.toString();
    }

    private static char getCharacterForBit(final int aBit) {
        if (aBit == 0) {
            return '0';
        }

        return '1';
    }
}
